/**
 ******************************************************************************
 * @file    AESUtils.c
 * @author  William Xu
 * @version V1.0.0
 * @date    05-May-2014
 * @brief   This file contains functiond called by the MXOS project.
 *          These functions abstract the interaction with AES libraries.
 ******************************************************************************
 *
 *  The MIT License
 *  Copyright (c) 2014 MXCHIP Inc.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is furnished
 *  to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 *  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 ******************************************************************************
 */

#include "AESUtils.h"

#include "mxos_common.h"
#include "mxos_debug.h"

#if( AES_UTILS_HAS_COMMON_CRYPTO_GCM )
    #include <CommonCrypto/CommonCryptorSPI.h>
#endif

#if( !AES_UTILS_USE_COMMON_CRYPTO && !AES_UTILS_USE_GLADMAN_AES && TARGET_NO_OPENSSL )
static
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
             const unsigned long length, const AES_KEY *key,
             unsigned char *ivec, const int enc);
#endif

#define aes_log(M, ...) custom_log("AES", M, ##__VA_ARGS__)

//===========================================================================================================================
//  AES_CTR_Init
//===========================================================================================================================

OSStatus
    AES_CTR_Init( 
        AES_CTR_Context *   inContext, 
        const uint8_t       inKey[ kAES_CTR_Size ], 
        const uint8_t       inNonce[ kAES_CTR_Size ] )
{
#if( AES_UTILS_USE_COMMON_CRYPTO )
    OSStatus        err;
    
    inContext->cryptor = NULL;
    err = CCCryptorCreate( kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode, inKey, kAES_CTR_Size, NULL, 
        &inContext->cryptor );
    check_noerr( err );
    if( err ) return( err );
#elif( AES_UTILS_USE_GLADMAN_AES )
    aes_init();
    aes_encrypt_key128( inKey, &inContext->ctx );
#elif( AES_UTILS_USE_MXOS_AES )
    AesSetKeyDirect(&inContext->ctx, (unsigned char *) inKey, AES_BLOCK_SIZE, inNonce, AES_ENCRYPTION);
#elif( AES_UTILS_USE_USSL )
    aes_setkey_enc( &inContext->ctx, (unsigned char *) inKey, kAES_CTR_Size * 8 );
#else
    AES_set_encrypt_key( inKey, kAES_CTR_Size * 8, &inContext->key );
#endif
    memcpy( inContext->ctr, inNonce, kAES_CTR_Size );
    inContext->used = 0;
    inContext->legacy = false;
    return( kNoErr );
}

//===========================================================================================================================
//  AES_CTR_Increment
//===========================================================================================================================

static inline void AES_CTR_Increment( uint8_t *inCounter )
{
    int     i;
    
    // Note: counter is always big endian so this adds from right to left.
    
    for( i = kAES_CTR_Size - 1; i >= 0; --i )
    {
        if( ++( inCounter[ i ] ) != 0 )
        {
            break;
        }
    }
}

//===========================================================================================================================
//  AES_CTR_Update
//===========================================================================================================================

OSStatus    AES_CTR_Update( AES_CTR_Context *inContext, const void *inSrc, size_t inLen, void *inDst )
{
    OSStatus            err;
    const uint8_t *     src;
    uint8_t *           dst;
    uint8_t *           buf;
    size_t              used;
    size_t              i;
    
    // inSrc and inDst may be the same, but otherwise, the buffers must not overlap.
    
#if( DEBUG )
    if( inSrc != inDst ) check_ptr_overlap( inSrc, inLen, inDst, inLen );
#endif
    
    src = (const uint8_t *) inSrc;
    dst = (uint8_t *) inDst;
    
    // If there's any buffered key material from a previous block then use that first.
    
    buf  = inContext->buf;
    used = inContext->used;
    while( ( inLen > 0 ) && ( used != 0 ) ) 
    {
        *dst++ = *src++ ^ buf[ used++ ];
        used %= kAES_CTR_Size;
        inLen -= 1;
    }
    inContext->used = used;
    
    // Process whole blocks.
    
    while( inLen >= kAES_CTR_Size )
    {
        #if( AES_UTILS_USE_COMMON_CRYPTO )
            err = CCCryptorUpdate( inContext->cryptor, inContext->ctr, kAES_CTR_Size, buf, kAES_CTR_Size, &i );
            require_noerr( err, exit );
            require_action( i == kAES_CTR_Size, exit, err = kSizeErr );
        #elif( AES_UTILS_USE_GLADMAN_AES )
            aes_ecb_encrypt( inContext->ctr, buf, kAES_CTR_Size, &inContext->ctx );
        #elif( AES_UTILS_USE_MXOS_AES )
            AesEncryptDirect( &inContext->ctx, buf, inContext->ctr );
        #elif( AES_UTILS_USE_USSL )
            aes_crypt_ecb( &inContext->ctx, AES_ENCRYPT, inContext->ctr, buf );
        #else
            AES_encrypt( inContext->ctr, buf, &inContext->key );
        #endif
        AES_CTR_Increment( inContext->ctr );
        
        for( i = 0; i < kAES_CTR_Size; ++i )
        {
            dst[ i ] = src[ i ] ^ buf[ i ];
        }
        src   += kAES_CTR_Size;
        dst   += kAES_CTR_Size;
        inLen -= kAES_CTR_Size;
    }
    
    // Process any trailing sub-block bytes. Extra key material is buffered for next time.
    
    if( inLen > 0 )
    {
        #if( AES_UTILS_USE_COMMON_CRYPTO )
            err = CCCryptorUpdate( inContext->cryptor, inContext->ctr, kAES_CTR_Size, buf, kAES_CTR_Size, &i );
            require_noerr( err, exit );
            require_action( i == kAES_CTR_Size, exit, err = kSizeErr );
        #elif( AES_UTILS_USE_GLADMAN_AES )
            aes_ecb_encrypt( inContext->ctr, buf, kAES_CTR_Size, &inContext->ctx );
        #elif( AES_UTILS_USE_MXOS_AES )
            AesEncryptDirect( &inContext->ctx, buf, inContext->ctr );
        #elif( AES_UTILS_USE_USSL )
            aes_crypt_ecb( &inContext->ctx, AES_ENCRYPT, inContext->ctr, buf );
        #else
            AES_encrypt( inContext->ctr, buf, &inContext->key );
        #endif
        AES_CTR_Increment( inContext->ctr );
        
        for( i = 0; i < inLen; ++i )
        {
            *dst++ = *src++ ^ buf[ used++ ];
        }
        
        // For legacy mode, always leave the used amount as 0 so we always increment the counter each time.
        
        if( !inContext->legacy )
        {
            inContext->used = used;
        }
    }
    err = kNoErr;
    
#if( AES_UTILS_USE_COMMON_CRYPTO )
exit:
#endif
    return( err );
}

//===========================================================================================================================
//  AES_CTR_Final
//===========================================================================================================================

void    AES_CTR_Final( AES_CTR_Context *inContext )
{
#if( AES_UTILS_USE_COMMON_CRYPTO )
    if( inContext->cryptor ) CCCryptorRelease( inContext->cryptor );
#endif
    memset( inContext, 0, sizeof( *inContext ) ); // Clear sensitive data.
}

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//  AES_CBCFrame_Init
//===========================================================================================================================

OSStatus
    AES_CBCFrame_Init( 
        AES_CBCFrame_Context *  inContext, 
        const uint8_t           inKey[ kAES_CBCFrame_Size ], 
        const uint8_t           inIV[ kAES_CBCFrame_Size ], 
        Boolean                 inEncrypt )
{
#if( AES_UTILS_USE_COMMON_CRYPTO )
    OSStatus        err;
    
    inContext->cryptor = NULL;
    err = CCCryptorCreate( inEncrypt ? kCCEncrypt : kCCDecrypt, kCCAlgorithmAES128, 0, inKey, kAES_CTR_Size, 
        NULL, &inContext->cryptor );
    check_noerr( err );
    if( err ) return( err );
#elif( AES_UTILS_USE_GLADMAN_AES )
    aes_init();
    if( inEncrypt ) aes_encrypt_key128( inKey, &inContext->ctx.encrypt );
    else            aes_decrypt_key128( inKey, &inContext->ctx.decrypt );
    inContext->encrypt = inEncrypt;
#elif( AES_UTILS_USE_MXOS_AES )
    if( inEncrypt ) AesSetKeyDirect(&inContext->ctx, (unsigned char *) inKey, AES_BLOCK_SIZE, inIV, AES_ENCRYPTION);
    else            AesSetKeyDirect(&inContext->ctx, (unsigned char *) inKey, AES_BLOCK_SIZE, inIV, AES_DECRYPTION);
#elif( AES_UTILS_USE_USSL )
    if( inEncrypt ) aes_setkey_enc( &inContext->ctx, (unsigned char *) inKey, kAES_CBCFrame_Size * 8 );
    else            aes_setkey_dec( &inContext->ctx, (unsigned char *) inKey, kAES_CBCFrame_Size * 8 );
    inContext->encrypt = inEncrypt;
#else
    if( inEncrypt ) AES_set_encrypt_key( inKey, kAES_CBCFrame_Size * 8, &inContext->key );
    else            AES_set_decrypt_key( inKey, kAES_CBCFrame_Size * 8, &inContext->key );
    inContext->mode = inEncrypt ? AES_ENCRYPT : AES_DECRYPT;
#endif
    memcpy( inContext->iv, inIV, kAES_CBCFrame_Size );
    return( kNoErr );
}

//===========================================================================================================================
//  AES_CBCFrame_Update
//===========================================================================================================================

OSStatus    AES_CBCFrame_Update( AES_CBCFrame_Context *inContext, const void *inSrc, size_t inSrcLen, void *inDst )
{
    OSStatus            err;
    const uint8_t *     src;
    const uint8_t *     end;
    uint8_t *           dst;
    size_t              len;
    
    src = (const uint8_t *) inSrc;
    end = src + inSrcLen;
    dst = (uint8_t *) inDst;
    
    // Process whole blocks.
    
    len = inSrcLen & ~( (size_t)( kAES_CBCFrame_Size - 1 ) );
    if( len > 0 )
    {
        #if( AES_UTILS_USE_COMMON_CRYPTO )
            err = CCCryptorReset(  inContext->cryptor, inContext->iv );
            require_noerr( err, exit );
            
            err = CCCryptorUpdate( inContext->cryptor, src, len, dst, len, &len );
            require_noerr( err, exit );
        #elif( AES_UTILS_USE_GLADMAN_AES )
            uint8_t     iv[ kAES_CBCFrame_Size ];
            
            memcpy( iv, inContext->iv, kAES_CBCFrame_Size ); // Use local copy so original IV is not changed.
            if( inContext->encrypt )    aes_cbc_encrypt( src, dst, (int) len, iv, &inContext->ctx.encrypt );
            else                        aes_cbc_decrypt( src, dst, (int) len, iv, &inContext->ctx.decrypt );
        #elif( AES_UTILS_USE_MXOS_AES )
            AesCbcEncrypt(&inContext->ctx, dst, src, len);
        #elif( AES_UTILS_USE_USSL )
            uint8_t     iv[ kAES_CBCFrame_Size ];

            memcpy( iv, inContext->iv, kAES_CBCFrame_Size ); // Use local copy so original IV is not changed.
            if( inContext->encrypt )    aes_crypt_cbc( &inContext->ctx, AES_ENCRYPT, len, iv, (unsigned char *) src, dst );
            else                        aes_crypt_cbc( &inContext->ctx, AES_DECRYPT, len, iv, (unsigned char *) src, dst );
        #else
            uint8_t     iv[ kAES_CBCFrame_Size ];
            
            memcpy( iv, inContext->iv, kAES_CBCFrame_Size ); // Use local copy so original IV is not changed.
            AES_cbc_encrypt( src, dst, (unsigned long) len, &inContext->key, iv, inContext->mode );
        #endif
        src += len;
        dst += len;
    }
    
    // The remaining bytes are just copied unencrypted.
    
    while( src != end ) *dst++ = *src++;
    err = kNoErr;
    
#if( AES_UTILS_USE_COMMON_CRYPTO )
exit:
#endif
    return( err );
}

//===========================================================================================================================
//  AES_CBCFrame_Update2
//===========================================================================================================================

OSStatus
    AES_CBCFrame_Update2( 
        AES_CBCFrame_Context *  inContext, 
        const void *            inSrc1, 
        size_t                  inLen1, 
        const void *            inSrc2, 
        size_t                  inLen2, 
        void *                  inDst )
{
    const uint8_t *     src1 = (const uint8_t *) inSrc1;
    const uint8_t *     end1 = src1 + inLen1;
    const uint8_t *     src2 = (const uint8_t *) inSrc2;
    const uint8_t *     end2 = src2 + inLen2;
    uint8_t *           dst  = (uint8_t *) inDst;
    OSStatus            err;
    size_t              len;
    size_t              i;
#if( !AES_UTILS_USE_COMMON_CRYPTO )
    uint8_t             iv[ kAES_CBCFrame_Size ];
#endif
    
#if( AES_UTILS_USE_COMMON_CRYPTO )
    if( ( inLen1 + inLen2 ) >= kAES_CBCFrame_Size )
    {
        err = CCCryptorReset(  inContext->cryptor, inContext->iv );
        require_noerr( err, exit );
    }
#else
    memcpy( iv, inContext->iv, kAES_CBCFrame_Size ); // Use local copy so original IV is not changed.
#endif
    
    // Process all whole blocks from buffer 1.
    
    len = inLen1 & ~( (size_t)( kAES_CBCFrame_Size - 1 ) );
    if( len > 0 )
    {
        #if( AES_UTILS_USE_COMMON_CRYPTO )
            err = CCCryptorUpdate( inContext->cryptor, src1, len, dst, len, &len );
            require_noerr( err, exit );
        #elif( AES_UTILS_USE_GLADMAN_AES )
            if( inContext->encrypt )    aes_cbc_encrypt( src1, dst, (int) len, iv, &inContext->ctx.encrypt );
            else                        aes_cbc_decrypt( src1, dst, (int) len, iv, &inContext->ctx.decrypt );
        #elif( AES_UTILS_USE_MXOS_AES )
            AesCbcEncrypt(&inContext->ctx, dst, src1, len);
        #elif( AES_UTILS_USE_USSL )
            if( inContext->encrypt )    aes_crypt_cbc( &inContext->ctx, AES_ENCRYPT, len, iv, (unsigned char *) src1, dst );
            else                        aes_crypt_cbc( &inContext->ctx, AES_DECRYPT, len, iv, (unsigned char *) src1, dst );
        #else
            AES_cbc_encrypt( src1, dst, (unsigned long) len, &inContext->key, iv, inContext->mode );
        #endif
        src1 += len;
        dst  += len;
    }
    
    // If there are any partial block bytes in buffer 1 and enough bytes in buffer 2 to fill a 
    // block then combine them into a temporary buffer and encrypt it.
    
    if( ( src1 != end1 ) && ( ( ( end1 - src1 ) + ( end2 - src2 ) ) >= kAES_CBCFrame_Size ) )
    {
        uint8_t     buf[ kAES_CBCFrame_Size ];
        
        for( i = 0; src1 != end1; ++i )
        {
            buf[ i ] = *src1++;
        }
        for( ; ( i < kAES_CBCFrame_Size ) && ( src2 != end2 ); ++i )
        {
            buf[ i ] = *src2++;
        }
        #if( AES_UTILS_USE_COMMON_CRYPTO )
            err = CCCryptorUpdate( inContext->cryptor, buf, i, dst, i, &i );
            require_noerr( err, exit );
        #elif( AES_UTILS_USE_GLADMAN_AES )
            if( inContext->encrypt )    aes_cbc_encrypt( buf, dst, (int) i, iv, &inContext->ctx.encrypt );
            else                        aes_cbc_decrypt( buf, dst, (int) i, iv, &inContext->ctx.decrypt );
        #elif( AES_UTILS_USE_MXOS_AES )
            AesCbcEncrypt(&inContext->ctx, dst, buf, i);
        #elif( AES_UTILS_USE_USSL )
            if( inContext->encrypt )    aes_crypt_cbc( &inContext->ctx, AES_ENCRYPT, i, iv, buf, dst );
            else                        aes_crypt_cbc( &inContext->ctx, AES_DECRYPT, i, iv, buf, dst );
        #else
            AES_cbc_encrypt( buf, dst, (unsigned long) i, &inContext->key, iv, inContext->mode );
        #endif
        dst += i;
    }
    
    // Process any remaining whole blocks in buffer 2.
    
    len = ( (size_t)( end2 - src2 ) ) & ~( (size_t)( kAES_CBCFrame_Size - 1 ) );
    if( len > 0 )
    {
        #if( AES_UTILS_USE_COMMON_CRYPTO )
            err = CCCryptorUpdate( inContext->cryptor, src2, len, dst, len, &len );
            require_noerr( err, exit );
        #elif( AES_UTILS_USE_GLADMAN_AES )
            if( inContext->encrypt )    aes_cbc_encrypt( src2, dst, (int) len, iv, &inContext->ctx.encrypt );
            else                        aes_cbc_decrypt( src2, dst, (int) len, iv, &inContext->ctx.decrypt );
        #elif( AES_UTILS_USE_MXOS_AES )
            AesCbcEncrypt(&inContext->ctx, dst, src2, len);
        #elif( AES_UTILS_USE_USSL )
            if( inContext->encrypt )    aes_crypt_cbc( &inContext->ctx, AES_ENCRYPT, len, iv, (unsigned char *) src2, dst );
            else                        aes_crypt_cbc( &inContext->ctx, AES_DECRYPT, len, iv, (unsigned char *) src2, dst );
        #else
            AES_cbc_encrypt( src2, dst, (unsigned long) len, &inContext->key, iv, inContext->mode );
        #endif
        src2 += len;
        dst  += len;
    }
    
    // Any remaining bytes are just copied unencrypted.
    
    while( src1 != end1 ) *dst++ = *src1++;
    while( src2 != end2 ) *dst++ = *src2++;
    err = kNoErr;
    
#if( AES_UTILS_USE_COMMON_CRYPTO )
exit:
#endif
    return( err );
}

//===========================================================================================================================
//  AES_CBCFrame_Final
//===========================================================================================================================

void    AES_CBCFrame_Final( AES_CBCFrame_Context *inContext )
{
#if( AES_UTILS_USE_COMMON_CRYPTO )
    if( inContext->cryptor ) CCCryptorRelease( inContext->cryptor );
#endif
    memset( inContext, 0, sizeof( *inContext ) ); // Clear sensitive data.
}

//===========================================================================================================================
//  AES_cbc_encrypt
//===========================================================================================================================

#if( !AES_UTILS_USE_COMMON_CRYPTO && !AES_UTILS_USE_GLADMAN_AES && !AES_UTILS_USE_MXOS_AES && TARGET_NO_OPENSSL )
// From OpenSSL
static
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
             const unsigned long length, const AES_KEY *key,
             unsigned char *ivec, const int enc) {

    unsigned long n;
    unsigned long len = length;
    unsigned char tmp[AES_BLOCK_SIZE];
    const unsigned char *iv = ivec;

    if (AES_ENCRYPT == enc) {
        while (len >= AES_BLOCK_SIZE) {
            for(n=0; n < AES_BLOCK_SIZE; ++n)
                out[n] = in[n] ^ iv[n];
            AES_encrypt(out, out, key);
            iv = out;
            len -= AES_BLOCK_SIZE;
            in += AES_BLOCK_SIZE;
            out += AES_BLOCK_SIZE;
        }
        if (len) {
            for(n=0; n < len; ++n)
                out[n] = in[n] ^ iv[n];
            for(n=len; n < AES_BLOCK_SIZE; ++n)
                out[n] = iv[n];
            AES_encrypt(out, out, key);
            iv = out;
        }
        memcpy(ivec,iv,AES_BLOCK_SIZE);
    } else if (in != out) {
        while (len >= AES_BLOCK_SIZE) {
            AES_decrypt(in, out, key);
            for(n=0; n < AES_BLOCK_SIZE; ++n)
                out[n] ^= iv[n];
            iv = in;
            len -= AES_BLOCK_SIZE;
            in  += AES_BLOCK_SIZE;
            out += AES_BLOCK_SIZE;
        }
        if (len) {
            AES_decrypt(in,tmp,key);
            for(n=0; n < len; ++n)
                out[n] = tmp[n] ^ iv[n];
            iv = in;
        }
        memcpy(ivec,iv,AES_BLOCK_SIZE);
    } else {
        while (len >= AES_BLOCK_SIZE) {
            memcpy(tmp, in, AES_BLOCK_SIZE);
            AES_decrypt(in, out, key);
            for(n=0; n < AES_BLOCK_SIZE; ++n)
                out[n] ^= ivec[n];
            memcpy(ivec, tmp, AES_BLOCK_SIZE);
            len -= AES_BLOCK_SIZE;
            in += AES_BLOCK_SIZE;
            out += AES_BLOCK_SIZE;
        }
        if (len) {
            memcpy(tmp, in, AES_BLOCK_SIZE);
            AES_decrypt(tmp, out, key);
            for(n=0; n < len; ++n)
                out[n] ^= ivec[n];
            for(n=len; n < AES_BLOCK_SIZE; ++n)
                out[n] = tmp[n];
            memcpy(ivec, tmp, AES_BLOCK_SIZE);
        }
    }
}
#endif

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//  AES_ECB_Init
//===========================================================================================================================

OSStatus    AES_ECB_Init( AES_ECB_Context *inContext, uint32_t inMode, const uint8_t inKey[ kAES_ECB_Size ] )
{
#if( AES_UTILS_USE_COMMON_CRYPTO )
    OSStatus        err;
    
    inContext->cryptor = NULL;
    err = CCCryptorCreate( inMode, kCCAlgorithmAES128, kCCOptionECBMode, inKey, kAES_ECB_Size, NULL, &inContext->cryptor );
    check_noerr( err );
    if( err ) return( err );
#elif( AES_UTILS_USE_GLADMAN_AES )
    aes_init();
    if( inMode == kAES_ECB_Mode_Encrypt )   aes_encrypt_key128( inKey, &inContext->ctx.encrypt );
    else                                    aes_decrypt_key128( inKey, &inContext->ctx.decrypt );
    inContext->encrypt = inMode;
#elif( AES_UTILS_USE_MXOS_AES )
    if( inMode == kAES_ECB_Mode_Encrypt )   AesSetKey( &inContext->ctx, inKey, kAES_ECB_Size, NULL, AES_ENCRYPTION );
    else                                    AesSetKey( &inContext->ctx, inKey, kAES_ECB_Size, NULL, AES_DECRYPTION );
#elif( AES_UTILS_USE_USSL )
    if( inMode == kAES_ECB_Mode_Encrypt )   aes_setkey_enc( &inContext->ctx, (unsigned char *) inKey, kAES_ECB_Size * 8 );
    else                                    aes_setkey_dec( &inContext->ctx, (unsigned char *) inKey, kAES_ECB_Size * 8 );
    inContext->mode = inMode;
#else
    AES_set_encrypt_key( inKey, kAES_ECB_Size * 8, &inContext->key );
    inContext->cryptFunc = ( inMode == kAES_ECB_Mode_Encrypt ) ? AES_encrypt : AES_decrypt;
#endif
    return( kNoErr );
}

//===========================================================================================================================
//  AES_ECB_Update
//===========================================================================================================================

OSStatus    AES_ECB_Update( AES_ECB_Context *inContext, const void *inSrc, size_t inLen, void *inDst )
{
    OSStatus            err;
    const uint8_t *     src;
    uint8_t *           dst;
    size_t              n;
    
    // inSrc and inDst may be the same, but otherwise, the buffers must not overlap.
    
#if( DEBUG )
    if( inSrc != inDst ) check_ptr_overlap( inSrc, inLen, inDst, inLen );
    if( ( inLen % kAES_ECB_Size ) != 0 ) aes_log( "ECB doesn't support non-block-sized operations (%d bytes)", (int)inLen );
#endif
    
    src = (const uint8_t *) inSrc;
    dst = (uint8_t *) inDst;
    for( n = inLen / kAES_ECB_Size; n > 0; --n )
    {
        #if( AES_UTILS_USE_COMMON_CRYPTO )
            size_t      len;
            
            err = CCCryptorUpdate( inContext->cryptor, src, kAES_ECB_Size, dst, kAES_ECB_Size, &len );
            require_noerr( err, exit );
            check( len == kAES_ECB_Size );
        #elif( AES_UTILS_USE_GLADMAN_AES )
            if( inContext->encrypt )    aes_ecb_encrypt( src, dst, kAES_ECB_Size, &inContext->ctx.encrypt );
            else                        aes_ecb_decrypt( src, dst, kAES_ECB_Size, &inContext->ctx.decrypt );
        #elif( AES_UTILS_USE_MXOS_AES )
            AesEncryptDirect( &inContext->ctx, dst, src );
        #elif( AES_UTILS_USE_USSL )
            aes_crypt_ecb( &inContext->ctx, inContext->mode, (unsigned char *) src, dst );
        #else
            inContext->cryptFunc( src, dst, &inContext->key );
        #endif
        src += kAES_ECB_Size;
        dst += kAES_ECB_Size;
    }
    err = kNoErr;
    
#if( AES_UTILS_USE_COMMON_CRYPTO )
exit:
#endif
    return( err );
}

//===========================================================================================================================
//  AES_ECB_Final
//===========================================================================================================================

void    AES_ECB_Final( AES_ECB_Context *inContext )
{
#if( AES_UTILS_USE_COMMON_CRYPTO )
    if( inContext->cryptor ) CCCryptorRelease( inContext->cryptor );
#endif
    memset( inContext, 0, sizeof( *inContext ) ); // Clear sensitive data.
}

#if 0
#pragma mark -
#endif

#if( AES_UTILS_HAS_GCM )

//===========================================================================================================================
//  AES_GCM_Init
//===========================================================================================================================

OSStatus
    AES_GCM_Init( 
        AES_GCM_Context *   inContext, 
        const uint8_t       inKey[ kAES_CGM_Size ], 
        const uint8_t       inNonce[ kAES_CGM_Size ] )
{
    OSStatus        err;
    
#if( AES_UTILS_HAS_COMMON_CRYPTO_GCM )
    err = CCCryptorCreateWithMode( kCCEncrypt, kCCModeGCM, kCCAlgorithmAES128, ccNoPadding, NULL, 
        inKey, kAES_CGM_Size, NULL, 0, 0, 0, &inContext->cryptor );
    require_noerr( err, exit );
#elif( AES_UTILS_HAS_GLADMAN_GCM )
    err = gcm_init_and_key( inKey, kAES_CGM_Size, &inContext->ctx );
    require_noerr( err, exit );
#else
    #error "GCM enabled, but no implementation?"
#endif
    
    if( inNonce ) memcpy( inContext->nonce, inNonce, kAES_CGM_Size );
    
exit:
    return( err );
}

//===========================================================================================================================
//  AES_GCM_Final
//===========================================================================================================================

void    AES_GCM_Final( AES_GCM_Context *inContext )
{
#if( AES_UTILS_HAS_COMMON_CRYPTO_GCM )
    if( inContext->cryptor ) CCCryptorRelease( inContext->cryptor );
#elif( AES_UTILS_HAS_GLADMAN_GCM )
    gcm_end( &inContext->ctx );
#else
    #error "GCM enabled, but no implementation?"
#endif
    memset( inContext, 0, sizeof( *inContext ) ); // Clear sensitive data.
}

//===========================================================================================================================
//  AES_GCM_InitMessage
//===========================================================================================================================

#if( AES_UTILS_HAS_COMMON_CRYPTO_GCM )
OSStatus    AES_GCM_InitMessage( AES_GCM_Context *inContext, const uint8_t inNonce[ kAES_CGM_Size ] )
{
    CCCryptorRef const      cryptor = inContext->cryptor;
    OSStatus                err;
    
    err = CCCryptorGCMReset( cryptor );
    require_noerr( err, exit );
    
    if( inNonce == kAES_CGM_Nonce_Auto )
    {
        AES_CTR_Increment( inContext->nonce );
        inNonce = inContext->nonce;
    }
    err = CCCryptorGCMAddIV( cryptor, inNonce, kAES_CGM_Size );
    require_noerr( err, exit );
    
exit:
    return( err );
}
#elif( AES_UTILS_HAS_GLADMAN_GCM )
OSStatus    AES_GCM_InitMessage( AES_GCM_Context *inContext, const uint8_t inNonce[ kAES_CGM_Size ] )
{
    OSStatus        err;
    
    if( inNonce == kAES_CGM_Nonce_Auto )
    {
        AES_CTR_Increment( inContext->nonce );
        inNonce = inContext->nonce;
    }
    err = gcm_init_message( inNonce, kAES_CGM_Size, &inContext->ctx );
    require_noerr( err, exit );
    
exit:
    return( err );
}
#endif

//===========================================================================================================================
//  AES_GCM_FinalizeMessage
//===========================================================================================================================

#if( AES_UTILS_HAS_COMMON_CRYPTO_GCM )
OSStatus    AES_GCM_FinalizeMessage( AES_GCM_Context *inContext, uint8_t outAuthTag[ kAES_CGM_Size ] )
{
    OSStatus        err;
    size_t          len;
    
    len = kAES_CGM_Size;
    err = CCCryptorGCMFinal( inContext->cryptor, outAuthTag, &len );
    require_noerr( err, exit );
    require_action( len == kAES_CGM_Size, exit, err = kSizeErr );
    
exit:
    return( err );
}
#elif( AES_UTILS_HAS_GLADMAN_GCM )
OSStatus    AES_GCM_FinalizeMessage( AES_GCM_Context *inContext, uint8_t outAuthTag[ kAES_CGM_Size ] )
{
    OSStatus        err;
    
    err = gcm_compute_tag( outAuthTag, kAES_CGM_Size, &inContext->ctx );
    require_noerr( err, exit );
    
exit:
    return( err );
}
#endif

//===========================================================================================================================
//  AES_GCM_VerifyMessage
//===========================================================================================================================

#if( AES_UTILS_HAS_COMMON_CRYPTO_GCM )
OSStatus    AES_GCM_VerifyMessage( AES_GCM_Context *inContext, const uint8_t inAuthTag[ kAES_CGM_Size ] )
{
    OSStatus        err;
    size_t          len;
    uint8_t         authTag[ kAES_CGM_Size ];
    
    len = kAES_CGM_Size;
    err = CCCryptorGCMFinal( inContext->cryptor, authTag, &len );
    require_noerr( err, exit );
    require_action( len == kAES_CGM_Size, exit, err = kSizeErr );
    require_action_quiet( memcmp_constant_time( authTag, inAuthTag, len ) == 0, exit, err = kAuthenticationErr );
    
exit:
    return( err );
}
#elif( AES_UTILS_HAS_GLADMAN_GCM )
OSStatus    AES_GCM_VerifyMessage( AES_GCM_Context *inContext, const uint8_t inAuthTag[ kAES_CGM_Size ] )
{
    OSStatus        err;
    uint8_t         authTag[ kAES_CGM_Size ];
    
    err = gcm_compute_tag( authTag, kAES_CGM_Size, &inContext->ctx );
    require_noerr( err, exit );
    require_action_quiet( memcmp_constant_time( authTag, inAuthTag, kAES_CGM_Size ) == 0, exit, err = kAuthenticationErr );
    
exit:
    return( err );
}
#endif

//===========================================================================================================================
//  AES_GCM_AddAAD
//===========================================================================================================================

OSStatus    AES_GCM_AddAAD( AES_GCM_Context *inContext, const void *inPtr, size_t inLen )
{
    OSStatus        err;
    
#if( AES_UTILS_HAS_COMMON_CRYPTO_GCM )
    err = CCCryptorGCMaddAAD( inContext->cryptor, inPtr, inLen );
    require_noerr( err, exit );
#elif( AES_UTILS_HAS_GLADMAN_GCM )
    err = gcm_auth_header( inPtr, inLen, &inContext->ctx );
    require_noerr( err, exit );
#else
    #error "GCM enabled, but no implementation?"
#endif
    
exit:
    return( err );
}

//===========================================================================================================================
//  AES_GCM_Encrypt
//===========================================================================================================================

#if( AES_UTILS_HAS_COMMON_CRYPTO_GCM )
OSStatus    AES_GCM_Encrypt( AES_GCM_Context *inContext, const void *inSrc, size_t inLen, void *inDst )
{
    OSStatus        err;
    
    err = CCCryptorGCMEncrypt( inContext->cryptor, inSrc, inLen, inDst );
    require_noerr( err, exit );
    
exit:
    return( err );
}
#elif( AES_UTILS_HAS_GLADMAN_GCM )
OSStatus    AES_GCM_Encrypt( AES_GCM_Context *inContext, const void *inSrc, size_t inLen, void *inDst )
{
    OSStatus        err;
    
    err = gcm_encrypt( inDst, inSrc, inLen, &inContext->ctx );
    require_noerr( err, exit );
    
exit:
    return( err );
}
#endif

//===========================================================================================================================
//  AES_GCM_Decrypt
//===========================================================================================================================

#if( AES_UTILS_HAS_COMMON_CRYPTO_GCM )
OSStatus    AES_GCM_Decrypt( AES_GCM_Context *inContext, const void *inSrc, size_t inLen, void *inDst )
{
    OSStatus        err;
    
    err = CCCryptorGCMDecrypt( inContext->cryptor, inSrc, inLen, inDst );
    require_noerr( err, exit );
    
exit:
    return( err );
}
#elif( AES_UTILS_HAS_GLADMAN_GCM )
OSStatus    AES_GCM_Decrypt( AES_GCM_Context *inContext, const void *inSrc, size_t inLen, void *inDst )
{
    OSStatus        err;
    
    err = gcm_decrypt( inDst, inSrc, inLen, &inContext->ctx );
    require_noerr( err, exit );
    
exit:
    return( err );
}
#endif
#endif


